#include "benchmark.hpp"
#include "exporter.hpp"

#include "cli.hpp"
#include "cliFunctions.hpp"
#include "settingsImport.hpp"
#include "settingsFunctions.hpp"
#include "importFunctions.hpp"
#include "loggerFunctions.hpp"

#include "exceptions.hpp"

#include "solverBase.hpp"
#include "sweSolverTypes_fwd.hpp"
#include "sweSolver.hpp"

#include "timeMarchingLoop.hpp"


namespace fs = std::filesystem;

#include "timer.hpp"


int main(int argc, char* argv[])
{
	using namespace hms;
	using namespace hms::vtk;
	namespace xml = tinyxml2;

	/* get working directory directly at startup, before
	 * other programs might have changed it */
	const fs::path wd { fs::current_path() };

	Settings settings;

	/* use external library to parse command line arguments. 
	 * Wrapper function returns std::optional, which holds an int on error or
	 * planned exit. Settings are written to the passed-in settings struct */
	std::optional<int> retVal { parseArgs(wd, settings, argc, argv) };
	if ( retVal )
		return retVal.value();

	/* make default logger quiet, if requested.
	 * Later, after log-related settings have been read from the ini,
	 * its level must be adjusted as well. */
	if ( settings.quiet ) spdlog::set_level( log::Level::off );
	std::shared_ptr<spdlog::logger> logger {nullptr};

	try {

	/* ===================================================== */
	/* read ini and get logger with proper settings */
	/* ===================================================== */
	readIni(settings);
	logger = log::getLogger();

	/* ===================================================== */
	/* other execution paths than running sim */
	/* ===================================================== */
	if ( settings.clearFiles ){
		clearCaseFiles(settings);
		return 0;
	}

	if ( settings.importItem.has_value() ){
		importData(settings);
		return 0;
	}

	if ( settings.runBenchmarkCase ){
		setBenchmarkSettings(settings);
		runBenchmark(settings);
		return 0;
	}

	/* ===================================================== */
	/* read mesh */
	/* ===================================================== */
	MeshVariant meshVar;
	xml::XMLDocument domainDoc, boundaryDoc;

	try {
		meshVar = readMesh(settings, &domainDoc, &boundaryDoc);
	} catch ( const std::exception& e ){
		logger->error(
			"{}\n"
			"{}-> {} Did you forget to {} it?",
			e.what(),
			indent(),
			bold(red("Mesh not found!")),
			bold("generate/import")
		);
		return 1;
	}


	/* ===================================================== */
	/* create solver and read fields */
	/* ===================================================== */
	Time time { std::move( settings.time() ) };

	SWESolverVariant solverVar { createSolver(
		settings, meshVar, time,
		&domainDoc, &boundaryDoc
	) };

	printCaseSetup(settings, solverVar);

	if (settings.dummyFileCreated){
		logger->info("Default files have been created, program will now exit.");
		return 0;
	}

	if (settings.dryRun){
		logger->info("Dry run ended, program will now exit.");
		return 0;
	}


	/* ===================================================== */
	/* run simulation */
	/* ===================================================== */
	log::printSection(logger, "Running simulation");

	/* read or create binding file */
	bool useExport { settings.exportSettings().exportResults };
	xml::XMLDocument bindingDoc;

	if (useExport){
		if (settings.resumeSimulation)
			getBindingDoc( &bindingDoc, settings.bindingFilePath() );
		else
			createBindingDoc( &bindingDoc );
	}

	std::visit( [&]( auto& solver ){
		timeMarchingLoop( solver, time, Exporter{
			useExport, settings.caseDir(), settings.caseName(),
			solver, bindingDoc, settings.bindingFilePath(),
			std::move( settings.probes() )
		} );
	}, solverVar );

	} catch ( const std::exception& e ){
		if (logger)
			logger->error( e.what() );
		else
			std::cerr << "Error: " << e.what() << '\n';
		return 1;
	}
	log::printSection(logger, "Simulation finished");
	
	return 0;
}